﻿/*                       NOTICE
 * 
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-10-C-0001, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * Approved for Public Release: 11-2435
 * © 2011 The MITRE Corporation. All Rights Reserved. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Drawing;
using RabbitMQ.Client;
using RabbitMQ.Client.MessagePatterns;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using System.Xml.Linq;
using System.Threading;

namespace GestureListenerClient.Services
{
    public class GestureClient
    {
        private Dictionary<int, Gestures.Gesture> gestureMap;
        private Dictionary<int, Gestures.FiducialObject> fiducialMap;

        private List<ListenerRegistration> listeners;
      
       

        // Gesture node relations in the XML document
        private static string GESTURE_NODE = "gesture";
        private static string OBJECT_NODE = "object";
        private static string GESTURE_ID_ATTR = "id";
        private static string GESTURE_INDEX_ATTR = "index";
        private static string GESTURE_TYPE_ATTR = "type";

        private static string GESTURE_TIME_ATTR = "time";

        private static string COORD_LIST_NODE = "coordinates";

        private static string COORD_NODE = "coordinate";
        private static string COORD_TYPE_ATTR = "type";
        private static string COORD_TYPE_CENTER = "CENTER";
        private static string COORD_TYPE_CONTACT = "CONTACT";
        
        private static string COORD_X_ATTR = "x";
        private static string COORD_Y_ATTR = "y";

        private string configFile;

        private IConnection conn;
        private IModel channel;
        private IModel objectChannel;
        string serverAddress;// = "localhost";
        string exchange;// = "grip";
        string queue;// = "grip-gestures";
        string objectQ;// = "grip-objects";
        string exchangeObject;// = "grip-fiducial";
        string routingKey;// = "";
        QueueingBasicConsumer consumer;
        QueueingBasicConsumer consumerObjects;
        Subscription subscription;
        Subscription subscriptionObjects;
        Thread workerThread;
        Thread workerThreadObjects;
        private Boolean listenOnQ = true;

        public GestureClient(String nickname, string configFile)
        {
            this.configFile = configFile;
            if (configFile != null)
                parseConfigSettings();

            this.gestureMap = new Dictionary<int, GestureListenerClient.Gestures.Gesture>();
            fiducialMap = new Dictionary<int, GestureListenerClient.Gestures.FiducialObject>();
            
            this.listeners = new List<ListenerRegistration>();
            ConnectionFactory factory = new ConnectionFactory();

            factory.HostName = serverAddress;
            factory.Port = AmqpTcpEndpoint.UseDefaultPort;
            //factory.Port = 3334;
            Console.WriteLine("Host = " + serverAddress + ", Port = " + factory.Port);
            conn = factory.CreateConnection();
            channel = conn.CreateModel();
            channel.ExchangeDeclare(exchange, ExchangeType.Fanout);
            channel.QueueDeclare(queue + nickname, true, false, true, null);
            channel.QueueBind(queue + nickname, exchange, routingKey, null);

            objectChannel = conn.CreateModel();
            Console.WriteLine("Starting Topic...and Q for Objects");
            objectChannel.ExchangeDeclare(exchangeObject, ExchangeType.Fanout);
            objectChannel.QueueDeclare(objectQ + nickname, true, false, true, null);
            objectChannel.QueueBind(objectQ + nickname, exchangeObject, routingKey, null);
            
            Console.WriteLine("Listening on Topic...and Q");

            consumer = new QueueingBasicConsumer(channel);
            //channel.BasicConsume(queue, true, null,consumer);
            subscription = new Subscription(channel, queue + nickname);
           

            // At this point, messages will be being asynchronously delivered,
            // and will be queueing up in consumer.Queue.
            workerThread = new Thread(processQueue);
            workerThread.Start();


            consumerObjects = new QueueingBasicConsumer(objectChannel);
            //channel.BasicConsume(queue, true, null,consumer);
            subscriptionObjects = new Subscription(objectChannel, objectQ + nickname);


            // At this point, messages will be being asynchronously delivered,
            // and will be queueing up in consumer.Queue.
            workerThreadObjects = new Thread(processQueueObjects);
            workerThreadObjects.Start();

           // serv = new HTTP.HTTPServer(this, portForCallback);
           // serv.startListening();

        }

        // Grabs the config parameters out of the given config file.
        protected void parseConfigSettings()
        {
            using (StreamReader reader = new StreamReader(configFile))
            {
                Dictionary<string, string> settingLookup = new Dictionary<string, string>();
                string line = reader.ReadLine();
                while (line != null)
                {
                    string[] elements = line.Split(':');
                    if (elements.Length > 1)
                        settingLookup.Add(elements[0], elements[1].TrimStart(' '));
                    line = reader.ReadLine();
                }

                serverAddress = settingLookup["serverAddress"];
                exchange = settingLookup["exchange"];
                exchangeObject = settingLookup["exchangeObject"];
                queue = settingLookup["queue"];
                objectQ = settingLookup["objectQ"];
                routingKey = settingLookup["routingKey"];

                reader.Close();
            }
        }

        void processQueue()
        {
            while (listenOnQ)
            {
                try
                {
                    foreach (BasicDeliverEventArgs e in subscription )
                    {
                       
                        Console.WriteLine("received message : " + e.BasicProperties.MessageId);
                        //float messageExpiration =float.Parse(e.BasicProperties.Expiration)*1000.0f;
                        
                        String body = System.Text.UTF8Encoding.UTF8.GetString(e.Body);
                        parseGesturesMsg(body);
                           
                        
                        subscription.Ack(e);
                    }
                   

                }
                catch (System.IO.EndOfStreamException ex)
                {
                    // The consumer was cancelled, the model closed, or the
                    // connection went away.
                    break;
                }
            }
        }

        void processQueueObjects()
        {
            while (listenOnQ)
            {
                try
                {
                    foreach (BasicDeliverEventArgs e in subscriptionObjects)
                    {

                        //Console.WriteLine("received message : " + e.BasicProperties.MessageId);
                        //float messageExpiration =float.Parse(e.BasicProperties.Expiration)*1000.0f;

                        String body = System.Text.UTF8Encoding.UTF8.GetString(e.Body);
                        parseObjectMsg(body);


                        subscriptionObjects.Ack(e);
                    }


                }
                catch (System.IO.EndOfStreamException ex)
                {
                    // The consumer was cancelled, the model closed, or the
                    // connection went away.
                    break;
                }
                catch (Exception e) {
                    Console.WriteLine("Exception in ack");
                }
               
            }
        }

        void parseGesturesMsg(String msg)
        {
            //Parse into gestures and alert our listener
            try
            {
                Gestures.Gesture[] gestures = parseXML(msg);
                foreach (Gestures.Gesture gesture in gestures)
                {
                    gestureOccurred(gesture);
                }
            }
            catch { }
        }

        void parseObjectMsg(String msg)
        {
            //Parse into objects and alert our listener
            try
            {
                Gestures.FiducialObject[] gestures = parseObjectXML(msg);
                foreach (Gestures.FiducialObject obj in gestures)
                {
                    objectOccurred(obj);
                }
            }
            catch { }
        }

        /// <summary>
        /// Parse a formatted xml document of gestures
        /// </summary>
        /// <param name="xmlGests">xml to parse</param>
        /// <returns>Array of parsed gesture objects</returns>
        private Gestures.FiducialObject[] parseObjectXML(string xmlGests)
        {
            List<Gestures.FiducialObject> gestList = new List<GestureListenerClient.Gestures.FiducialObject>();
            //LINQ to break xml into readable nodes
            var xDoc = XElement.Parse(xmlGests).Elements(OBJECT_NODE);
            foreach (var gestElem in xDoc)
            {
                try
                {
                    gestList.Add(parseObject(gestElem));
                }
                catch { }
            }
            return gestList.ToArray();
        }

        

        public void registerListener(ListenerRegistration listener)
        {
            this.listeners.Add(listener);

            //TODO: POST all types for the delegate (tap, doubletap, etc).... 
           /* foreach (var item in listener.CallbackDelegate.eventMap.Keys)
            {
                string gestName = GestureReflection.GESTURE_CLASS_MAP.Single(y => y.Value.Equals(item)).Key;
                string postStr = "gesture=" + gestName + "&host=" + serv.ServerPath.AbsoluteUri;
                HTTP.HTTPClient.post(GestureService, postStr);
            }
            * */
        }

        /// <summary>
        /// Remove a listener from getting gesture updates
        /// </summary>
        /// <param name="listener">Listener to remove</param>
        public void unRegisterListener(ListenerRegistration listener)
        {
            this.listeners.Remove(listener);

            //TODO: POST to server
            throw new System.NotImplementedException();
        }

        /// <summary>
        /// Removes all of the listeners.
        /// </summary> 
        public void unregisterAll()
        {
            listeners.Clear();
        }

        /// <summary>
        /// Un-register all listeners before destroying
        /// </summary>
        public void destroy()
        {
           // serv.stopListening();
            if (workerThread.IsAlive)
            {
                listenOnQ = false;
            }
            //TODO: POST to server

            //throw new System.NotImplementedException();
        }

        /// <summary>
        /// Parse a formatted xml document of gestures
        /// </summary>
        /// <param name="xmlGests">xml to parse</param>
        /// <returns>Array of parsed gesture objects</returns>
        private Gestures.Gesture[] parseXML(string xmlGests)
        {
            List<Gestures.Gesture> gestList = new List<GestureListenerClient.Gestures.Gesture>();
            //LINQ to break xml into readable nodes
            var xDoc = XElement.Parse(xmlGests).Elements(GESTURE_NODE);
            foreach (var gestElem in xDoc)
            {
                try
                {
                    gestList.Add(parseGesture(gestElem));
                }
                catch(Exception e)
                {
                    Console.WriteLine(e.Message);
                }
            }
            return gestList.ToArray();
        }


        public void gestureOccurred(Gestures.Gesture gesture)
        {
            if (!gestureMap.ContainsKey(gesture.ID)) gestureMap.Add(gesture.ID, gesture);
            else
            {
                gesture.PreviousGesture = gestureMap[gesture.ID];
                gestureMap[gesture.ID] = gesture;
            }

            foreach (ListenerRegistration listener in listeners)
            {
                if (listener.CallbackDelegate != null)
                {
                    //Check and see if this gesture falls in the bounds for this listener
                    bool caresAboutGest = true;
                    foreach (PointF point in gesture.Coords)
                    {
                        if (!listener.CaptureBounds.Contains(point))
                        {
                            caresAboutGest = false;
                            break;
                        }
                    }
                    if (caresAboutGest && listener.CallbackDelegate.eventMap.Keys.Contains(gesture.GetType()))
                    {
                        listener.CallbackDelegate.eventMap[gesture.GetType()](this, new GestureListenerClient.Services.Delegates.GestureArgs { Gesture = gesture });
                    }
                }
            }
        }

        public void objectOccurred(Gestures.FiducialObject gesture)
        {
            if (!fiducialMap.ContainsKey(gesture.ID)) fiducialMap.Add(gesture.ID, gesture);
            else
            {

                fiducialMap[gesture.ID] = gesture;
            }

            foreach (ListenerRegistration listener in listeners)
            {
                if (listener.FiducialCallbackDelegate != null)
                {
                    //Check and see if this gesture falls in the bounds for this listener
                    bool caresAboutGest = true;
                    foreach (PointF point in gesture.Coords)
                    {
                        if (!listener.CaptureBounds.Contains(point) || listener.FiducialCallbackDelegate == null)
                        {
                            caresAboutGest = false;
                            break;
                        }
                    }
                    if (caresAboutGest && listener.FiducialCallbackDelegate.eventMap.Keys.Contains(gesture.GetType()))
                    {
                        listener.FiducialCallbackDelegate.eventMap[gesture.GetType()](this, new GestureListenerClient.Services.Delegates.FiducialArgs { Fiducial = gesture });
                    }
                }
            }
        }

        private Gestures.Gesture parseGesture(XElement gestElem)
        {
            //Get the gesture type
            string gestTypeStr = gestElem.Attribute(GESTURE_TYPE_ATTR).Value;
            //Use map to see what gesture class to instantiate
            Type gestType = GestureReflection.GESTURE_CLASS_MAP[gestTypeStr];
            if (gestType != null)
            {
                int id = -1;
                if (gestElem.Attribute(GESTURE_ID_ATTR) != null) id = int.Parse(gestElem.Attribute(GESTURE_ID_ATTR).Value);

                //index on screen
                int index = -1;
                if (gestElem.Attribute(GESTURE_INDEX_ATTR) != null) index = int.Parse(gestElem.Attribute(GESTURE_INDEX_ATTR).Value);

                //timestamp gesture occurred
                DateTime timestamp = DateTime.Parse(gestElem.Attribute(GESTURE_TIME_ATTR).Value);

                //Get the coordinates of the gesture
                List<PointF> coords = new List<PointF>();
                PointF center = PointF.Empty;
                foreach (var gestCoord in gestElem.Elements(COORD_LIST_NODE).Elements(COORD_NODE))
                {
                    if (gestCoord.Attribute(COORD_TYPE_ATTR).Value.Equals(COORD_TYPE_CONTACT, StringComparison.OrdinalIgnoreCase))
                    {
                        float x = float.Parse(gestCoord.Attribute(COORD_X_ATTR).Value);
                        float y = float.Parse(gestCoord.Attribute(COORD_Y_ATTR).Value);
                        coords.Add(new PointF { X = x, Y = y });
                    }
                    else if (gestCoord.Attribute(COORD_TYPE_ATTR).Value.Equals(COORD_TYPE_CENTER, StringComparison.OrdinalIgnoreCase))
                    {
                        float x = float.Parse(gestCoord.Attribute(COORD_X_ATTR).Value);
                        float y = float.Parse(gestCoord.Attribute(COORD_Y_ATTR).Value);
                        center = new PointF { X = x, Y = y };
                    }
                }

                //Create the gesture using reflection
                Gestures.Gesture gestToAdd = Activator.CreateInstance(gestType, new object[] { id, index, timestamp, coords.ToArray() }) as Gestures.Gesture;
                if (gestToAdd != null) gestToAdd.Center = center;

                return gestToAdd;
            }
            else throw new System.FormatException("Invalid gesture type recognized. " + gestTypeStr + " is not a valid gesture");
        }


        private Gestures.FiducialObject parseObject(XElement gestElem)
        {
            //Get the gesture type
            string gestTypeStr = gestElem.Attribute(GESTURE_TYPE_ATTR).Value;
            //Use map to see what gesture class to instantiate
            Type gestType = GestureReflection.GESTURE_CLASS_MAP[gestTypeStr];
            if (gestType != null)
            {
                int id = -1;
                if (gestElem.Attribute(GESTURE_ID_ATTR) != null) id = int.Parse(gestElem.Attribute(GESTURE_ID_ATTR).Value);

                //index on screen
                int index = -1;
                if (gestElem.Attribute(GESTURE_INDEX_ATTR) != null) index = int.Parse(gestElem.Attribute(GESTURE_INDEX_ATTR).Value);

                //timestamp gesture occurred
                DateTime timestamp = DateTime.Parse(gestElem.Attribute(GESTURE_TIME_ATTR).Value);

                //Get the coordinates of the gesture
                List<PointF> coords = new List<PointF>();
                PointF center = PointF.Empty;
                foreach (var gestCoord in gestElem.Elements(COORD_LIST_NODE).Elements(COORD_NODE))
                {
                    if (gestCoord.Attribute(COORD_TYPE_ATTR).Value.Equals(COORD_TYPE_CONTACT, StringComparison.OrdinalIgnoreCase))
                    {
                        float x = float.Parse(gestCoord.Attribute(COORD_X_ATTR).Value);
                        float y = float.Parse(gestCoord.Attribute(COORD_Y_ATTR).Value);
                        coords.Add(new PointF { X = x, Y = y });
                    }
                    else if (gestCoord.Attribute(COORD_TYPE_ATTR).Value.Equals(COORD_TYPE_CENTER, StringComparison.OrdinalIgnoreCase))
                    {
                        float x = float.Parse(gestCoord.Attribute(COORD_X_ATTR).Value);
                        float y = float.Parse(gestCoord.Attribute(COORD_Y_ATTR).Value);
                        center = new PointF { X = x, Y = y };
                    }
                }

                //Create the gesture using reflection
                Gestures.FiducialObject gestToAdd = Activator.CreateInstance(gestType, new object[] { id, index, timestamp, coords.ToArray() }) as Gestures.FiducialObject;
                if (gestToAdd != null) gestToAdd.Center = center;

                return gestToAdd;
            }
            else throw new System.FormatException("Invalid gesture type recognized. " + gestTypeStr + " is not a valid gesture");
        }
    }
}
